import java.io.*;

/**
 * 
 * @author andipandi
 *
 */
public class NodeRecorder {
	
	/**
	 * dummy segment fill space occupied by for instance info packets
	 */
	//public static Segment DUMMY = new Segment(0, Segment.SEG_TYPE_DUMMY, null, 0, 0);

	/**
	 * Attributes
	 */
	public int 			node_id;
	public InfoPacket 		infoPacket;
	public int 	num_files = 0;
	public long bytes_received;
	public long bytes_buffered;
	
	
	
	///< ringbuffer of segments
	Segment segments[] = new Segment[Segment.MAX_SEGMENTS];
	
	/**
	 * private stuff
	 */
	private GatewayListener listener;
	private FileOutputStream fw = null;
	private int seg_counter = 0;
	
	private int head_seg = 0;
	//private int tail_seg = 0;
	
	//for timeout
	private long time_last_written = 0;
	
	/**
	 * transfer rate
	 */
	public double bps = 0;
	public double average_bps = 0;
	private long bytes_body = 0; //all received bytes withoud first segment
	private long tx_bytes = 0;
	private long tx_time = 0;// = System.currentTimeMillis();
	private long secs_received = 0;
	
	/**
	 * loss rate
	 */
	public double loss_rate = 0;
	private long bytes_missing = 0;
	
	/**
	 * constructor
	 */
	NodeRecorder(int id, GatewayListener gl){
		System.out.println("new node " + id);
		node_id = id;
		listener = gl;
	}
	
	/**
	 * 
	 * @param millis timeout
	 */
	void checkTimeout(long millis){
		//System.out.print("["+node_id+"]");
		
		//reset data transfer rate
		tx_bytes = 0;
		bps = 0;
		tx_time = System.currentTimeMillis();
		
		if(bytes_buffered <= 0)
			return;
		
		if((System.currentTimeMillis()- time_last_written) >= millis){
			System.out.println("["+node_id+"]" + "Timeout!");
			
			force_flush(false);
			
			//flush more if possible
			flush();
		}
		
		listener.dataEvent(node_id);
		

	}
	
	
	/**
	 * 
	 */
	public void close(){
		int tail_seg = (head_seg-1)& Segment.SEG_MASK;

		force_flush(true);//write all
	}
	
	/**
	 * 
	 * @param s
	 */
	void writeSegment(Segment s){
		
		/**
		 * statistics
		 */
		if(s.type != s.SEG_TYPE_DUMMY){
			bytes_received += 110;
			if(tx_time>0){
				tx_bytes += 110;
				long now = System.currentTimeMillis();
				long diff = now - tx_time;
				bps = ((double)tx_bytes*8.)/(((double)diff)/((double)1000));
			}
			else{//first segment:(minus first adv slot)
				tx_time = System.currentTimeMillis();
			}
		}
	
		
		//System.out.print("["+node_id+"]" + " h="+ head_seg );
		//System.out.print(" seg " + s.nr);
		
		//free space
		while(segments[s.nr] != null){
			System.out.println(" occupied!");
			//flush(false);
		}
		
		if(s.type == Segment.SEG_TYPE_FIRST){
			
			//System.out.print(" start");
			//last file not completed?
			if(fw != null){
				//buffer seg
				segments[s.nr] = s;
				bytes_buffered += s.data.length;
				//System.out.println(" buffered");
				return;
			}
			//System.out.print(" written. ");
			openFile();
			append(s);
			
			head_seg = (s.nr + 1) & Segment.SEG_MASK;
			//new tail?

		}
		else if(s.type == Segment.SEG_TYPE_MID){
			//System.out.print(" mid");
			
			if(fw == null || head_seg != s.nr ){
				//buffer seg
				segments[s.nr] = s;
				bytes_buffered += s.data.length;
				//System.out.println(" buffered");
				return;
			}
			//System.out.print(" written. ");
		
			append(s);
					
			nextSegment();
		}
		else if(s.type == Segment.SEG_TYPE_LAST){ //last segment
			
//			//average transfer rate
//			secs_received += diff;
//			average_bps = (bytes_body*8)/(((double)bytes_received)/((double)1000));
//			//reset rate
//			last_time = now;
//			tx_bytes = s.data.length;
			
			//System.out.print(" last");
			if(fw == null || head_seg != s.nr ){
				//buffer seg
				segments[s.nr] = s;
				bytes_buffered += s.data.length;
				//System.out.println(" buffered");
				return;
			}
			//System.out.print(" written. ");
			
			append(s);
			closeFile();		
			nextSegment();
			
		}
		else{//Dummy!!! 
			//System.out.print(" dummy");
			if(head_seg == s.nr){
				//System.out.print(" dropped.");
				nextSegment();
			}
			else{
				//buffer seg
				segments[s.nr] = s;
				//System.out.println("  buffered");
			}
		}
		
		flush();
	}
	
	/**
	 * 
	 */
	private void flush(){
		Segment s;
		//write as much as possible
		while(true){
			s = segments[head_seg];
			if(s == null){
				return;
			}
			
			if(s.type == Segment.SEG_TYPE_FIRST){
				if(fw == null){
					bytes_buffered -= s.data.length;
					openFile();
					//System.out.print(" read " + s.nr + " start written");
					append(s);
				}
				else{
					//System.out.println("wait for last segment.");
					return;
				}
			}
			else if(s.type == Segment.SEG_TYPE_MID){
				if(fw != null){
					bytes_buffered -= s.data.length;
					//System.out.print("read " + s.nr + " mid written");
					append(s);
				}
				else{
					//System.out.println("wait for first segment.");
					return;
				}
			}
			else if(s.type == Segment.SEG_TYPE_LAST){
				if(fw != null){
					bytes_buffered -= s.data.length;
					//System.out.print("read " + s.nr + " last written");
					append(s);
					closeFile();
				}
				else{
					//System.out.println("record ends. wait for first segment.");
					return;
				}
			}
			else if(s.type == Segment.SEG_TYPE_DUMMY){
				//System.out.println("dummy removed.");
				//remove dummy
				segments[head_seg] = null;
			}
			else
				System.out.println("segment error.");
			segments[head_seg] = null;
			nextSegment();
			
		}
		
	}
	
	private void nextSegment(){
		head_seg = (head_seg + 1) & Segment.SEG_MASK;
	}
	
	/**
	 * opens the next node record file
	 */
	private void openFile(){
		
		if(fw == null){
			try{
				fw = new FileOutputStream(new File( node_id + "-" + num_files + ".log"));
				num_files++;
				System.out.println( "open " + node_id + "-" + num_files + ".log");
			}catch(IOException ioe){
				listener.msgEvent("Error opening output file:" + ioe.getMessage());
			}
		}
	}
	
	private void closeFile(){
		System.out.println( "close file" );
		if(fw != null){
			try{
				fw.close();
				fw = null;
				seg_counter = 0;
			}catch(IOException ioe){
				listener.msgEvent("Error closing output file:" + ioe.getMessage());
			}
		}	
	}
	
	/**
	 * appends segment to the end of currently open file
	 */
	private void append(Segment s){
		try{
			fw.write( s.data);
			fw.flush();
			seg_counter ++;
			time_last_written = System.currentTimeMillis();
			//listener.msgEvent("SEG " + s.nr+ " written->" + (recordCounter-1) + "\n");
		}catch(IOException ioe){
			System.out.println("Error writing to file:" + ioe.getMessage());
		}
	}
	
	/**
	 * writes all segments from the first segment to next terminating segment
	 * missing segments are written as a zero array 
	 */
	private void force_flush(boolean all){
		
		//System.out.print("f");
			
		while(bytes_buffered > 0){

			Segment s = segments[head_seg];
			
			if(s != null ){
				
				if(s.type == Segment.SEG_TYPE_FIRST){
					
					//old file open?
					if(fw != null){
						closeFile();
					
						
						//end segment missing
						System.out.println( "last SEG " + head_seg + "missing !");
						bytes_missing += Segment.SEG_SIZE;
						loss_rate = ((double)bytes_missing)/((double)(bytes_received+bytes_missing));
					
						if(!all)
							return;
					}
					
					openFile();
					append(s);
					bytes_buffered -= s.data.length;
					segments[head_seg] = null;
					
					nextSegment();
					
				}
				else if(s.type == Segment.SEG_TYPE_LAST){
					
					if(fw == null){
						openFile();//writing just one segment into file
						
						//at least start segment is missing
						//end segment missing
						System.out.println( "first SEG " + head_seg + "missing !");
						bytes_missing += Segment.SEG_SIZE;
						loss_rate = ((double)bytes_missing)/((double)(bytes_received+bytes_missing));
					}
					
					append(s);
					closeFile();
					bytes_buffered -= s.data.length;
					segments[head_seg] = null;
					
					nextSegment();
					
					if(!all)
						return;
				}
				else if(s.type == Segment.SEG_TYPE_MID){//middle segment
				
					if(fw == null){
						openFile();
						System.out.println( "start SEG " + head_seg + "missing !");
						//start segment is missing
						bytes_missing += Segment.SEG_SIZE;
						loss_rate = ((double)bytes_missing)/((double)(bytes_received+bytes_missing));
					}
					append(s);
					bytes_buffered -= s.data.length;
					segments[head_seg] = null;
					
					nextSegment();
				}
				else{// dummy segment
					//removing dummy
					segments[head_seg] = null;
					nextSegment();
				}
			}
			else{

				//middle segment missing
				bytes_missing += Segment.SEG_SIZE;
				loss_rate = ((double)bytes_missing)/((double)(bytes_received+bytes_missing));

				if(fw == null){
					openFile();
					//start seg missing, too
					bytes_missing += Segment.SEG_SIZE;
					loss_rate = ((double)bytes_missing)/((double)(bytes_received+bytes_missing));
				}
				
				System.out.println( "mid SEG " + head_seg + "missing !");
				//write 110 zeros
				byte zeros[] = new byte[Segment.SEG_SIZE];
				for(int i=0; i<Segment.SEG_SIZE; i++)
					zeros[i] =0;
				try{
					for(int i=0; i<Segment.SEG_SIZE; i++)
						fw.write(zeros);
				}catch(IOException ioe){
					listener.msgEvent("Error writing to file:" + ioe.getMessage());
				}
				
				nextSegment();
			}
		}
		closeFile();
			
	}
}
